﻿using Bean.SqlSugar.Core;
using Microsoft.Extensions.Options;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bean.Repository
{
    public class Repository<T> : SimpleClient<T>, IRepository<T>, IDisposable where T : class, new()
    {

        /// <summary>
        /// 打印SQL语句到控制台
        /// </summary>
        private bool OUTPUT_SQL_IN_CONSOLE = true;
        private readonly SqlSugarOptions _options;

        public Repository()
        {

        }

        public Repository(IOptionsMonitor<SqlSugarOptions> optionAccs)
        {
            _options = optionAccs.CurrentValue;
            base.Context = GetSqlSugarClient();
        }

        private ISqlSugarClient GetSqlSugarClient()
        {
            var slaveConnectionConfigs = new List<SlaveConnectionConfig>();//从库集合
            Check.Exception(_options == null && _options.MasterDatabase == null, "ConnectionConfig config is null");
            //主库连接字符串
            string masterConnectionString = GetConnectionString(_options.MasterDatabase, _options.DbType);
            if (_options.SlaveDatabase != null)
            {
                //从库配置
                foreach (var option in _options.SlaveDatabase)
                {
                    slaveConnectionConfigs.Add(new SlaveConnectionConfig()
                    {
                        //查询走从库，事务内都走主库，HitRate表示权重 值越大执行的次数越高
                        HitRate = option.HitRate,
                        ConnectionString = GetConnectionString(option, _options.DbType)
                    });
                }
            }

            //如果配置了 SlaveConnectionConfigs那就是主从模式,所有的写入删除更新都走主库，查询走从库，
            //事务内都走主库，HitRate表示权重 值越大执行的次数越高，如果想停掉哪个连接可以把HitRate设为0 
            var DbContext = new SqlSugarClient(new ConnectionConfig()
            {
                //主库连接字符串
                ConnectionString = masterConnectionString,
                //数据库类型
                DbType = _options.DbType,
                //该设置表示业务完成后是否需要调用connection.close()函数。
                //如果程序并发不大的时候可以考虑设置成False，但要小心Throw Exception：There is already an open DataReader associated with this Connection which must be closed first.
                //但是如果连接字符串中设置pooling=true以后，则ADO.NET底层帮我们开启了连接池，业务完成以后connection.close()也就不是真正的关闭了物理连接，只是把我们的连接放到池里面。
                //当我们下一次connection.open()就会从池中找一个空闲的物理连接给我们，如果没有就会新建。但是超过我们设置max pool size 以后，就会出现wait。
                IsAutoCloseConnection = true,
                //如果配置了 SlaveConnectionConfigs那就是主从模式,所有的写入删除更新都走主库，
                //查询走从库，事务内都走主库，HitRate表示权重 值越大执行的次数越高，
                //如果想停掉哪个连接可以把HitRate设为0
                SlaveConnectionConfigs = slaveConnectionConfigs,
                //IsShardSameThread = true,
                InitKeyType = InitKeyType.Attribute,
                //动态切换数据源的参数配置
                //IsDynamicDataSource = _options.IsDynamicDataSource,
                //散列算法
                //AlgorithmEnum = _options.HashAlgorithmEnum,
                //当使用直接指定的尾缀时，该散列因子可以不用传
                //当时用Hash算法散列数据时，该因子值为散列的取摩的除数
                //HashFactor = _options.HashFactor,
                //DatabaseNamePrefix = _options.DatabaseNamePrefix
            });
            DbContext.Ado.CommandTimeOut = 60000;//设置超时时间
            DbContext.Aop.OnLogExecuted = (sql, pars) => //SQL执行完事件
            {
                //LogHelper.WriteLog($"执行时间：{db.Ado.SqlExecutionTime.TotalMilliseconds}毫秒 \r\nSQL如下：{sql} \r\n参数：{GetParams(pars)} ", "SQL执行");
            };
            DbContext.Aop.OnLogExecuting = (sql, pars) => //SQL执行前事件
            {
                if (DbContext.TempItems == null) DbContext.TempItems = new Dictionary<string, object>();
            };
            DbContext.Aop.OnError = (exp) =>//执行SQL 错误事件
            {
                //LogHelper.WriteLog($"SQL错误:{exp.Message}\r\nSQL如下：{exp.Sql}", "SQL执行");
                throw new Exception(exp.Message);
            };
            DbContext.Aop.OnExecutingChangeSql = (sql, pars) => //SQL执行前 可以修改SQL
            {
#if DEBUG
                OUTPUT_SQL_IN_CONSOLE = true;
#endif

                if (OUTPUT_SQL_IN_CONSOLE)
                {
                    var sqlq = UtilMethods.GetSqlString(DbType.MySql, sql, pars);
                    Console.WriteLine($"[{DateTime.Now}]执行sql=>[{sqlq}]");
                }
                return new KeyValuePair<string, SugarParameter[]>(sql, pars);
            };
            DbContext.Aop.OnDiffLogEvent = (it) => //可以方便拿到 数据库操作前和操作后的数据变化。
            {
                //var editBeforeData = it.BeforeData;
                //var editAfterData = it.AfterData;
                //var sql = it.Sql;
                //var parameter = it.Parameters;
                //var data = it.BusinessData;
                //var time = it.Time;
                //var diffType = it.DiffType;//枚举值 insert 、update 和 delete 用来作业务区分

                //TODO:something for you logger
            };
            return DbContext;
        }


        /// <summary>
        ///  获得SqlSugarClient
        /// </summary>
        private string GetConnectionString(Master options, DbType dbType)
        {
            string connectionString = string.Empty;
            switch (dbType)
            {
                case DbType.SqlServer:
                    connectionString = $"server={options.ServerAddr};user id={options.User};password={options.Password};persistsecurityinfo=True;database={options.DatabaseName}";
                    break;
                case DbType.MySql:
                    //可以在连接字符串中设置连接池pooling=true;表示开启连接池
                    //eg:min pool size=2;max poll size=4;表示最小连接池为2，最大连接池是4；默认是100
                    if (string.IsNullOrEmpty(options.DatabaseName))
                        connectionString = $"Server={options.ServerAddr};port={options.Port};Uid={options.User};Pwd={options.Password};charset='utf8';pooling={options.Pooling};min pool size={options.MinPoolSize};max pool size={options.MaxPoolSize};AllowLoadLocalInfile=true;";
                    else
                        connectionString = $"Server={options.ServerAddr};port={options.Port};Database={options.DatabaseName};Uid={options.User};Pwd={options.Password};charset='utf8';pooling={options.Pooling};min pool size={options.MinPoolSize};max pool size={options.MaxPoolSize};AllowLoadLocalInfile=true;";
                    break;
                case DbType.Oracle:
                    connectionString = $"Server=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST={options.ServerAddr})(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME={options.DatabaseName})));User Id={options.User};Password={options.Password};Persist Security Info=True;Enlist=true;Max Pool Size=300;Min Pool Size=0;Connection Lifetime=300";
                    break;
                case DbType.PostgreSQL:
                    connectionString = $"PORT=5432;DATABASE={options.DatabaseName};HOST={options.ServerAddr};PASSWORD={options.Password};USER ID={options.User}";
                    break;
                case DbType.Sqlite:
                    connectionString = $"Data Source={options.ServerAddr};Version=3;Password={options.Password};";
                    break;
            }
            return connectionString;
        }




        public void Dispose()
        {
            Context.Ado.Dispose();
            Context?.Dispose();
        }
    }
}
